﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Raona.Code.Contracts.EventArguments;
using Raona.Code.Model;
using System.Reflection;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.ServiceModel;
using System.Threading;
using Raona.Code.Contracts.Inspection;
//using System.Threading.Tasks.Schedulers;

namespace Raona.Code.AssemblyInspection
{
    public class AssemblyOperationContractInspector2 : AssemblyOperationContractInspectorBase, IOperationContractInspector
    {
        #region Events (Public)

        #endregion

        #region Instance Fields (Private)

        /// <summary>
        /// The source of cancellation tokens for the inspection process
        /// </summary>
        private CancellationTokenSource cancellationTokenSource = null;

        // The code inspection result is a collection of namespaces
        // containing the interfaces found

        /// <summary>
        /// Indicates whether the inspection process is single threaded or not
        /// </summary>
        private bool useSingleThreadedMode = false;

        /// <summary>
        /// The scheduler used by the inspection process
        /// </summary>
        private TaskScheduler scheduler = null;

        #endregion

        #region Instance Constructors (Public)

        public AssemblyOperationContractInspector2(string contractsAssemblyPath)
        {
            this.namespaces = new ConcurrentDictionary<string, Namespace>();
            this.contractsAssemblyPath = contractsAssemblyPath;
        }

        #endregion

        #region Instance Properties (Public)

        #endregion

        #region Instance Methods (Public)

        /// <summary>
        /// Runs an inspection process synchronously
        /// </summary>
        /// <param name="clientBase">The base class for the client class</param>
        /// <param name="serviceContractsNamespace">The xml namespace for the generated contracts</param>
        public Dictionary<string, Namespace> GetInterfaces(string clientBase, string serviceContractsNamespace)
        {
            Dictionary<string, Namespace> result = new Dictionary<string, Namespace>();

            this.cancellationTokenSource = new CancellationTokenSource();

            Task<Namespace>[] tasks = this.CreateInspectionTasks(clientBase, serviceContractsNamespace);
            Task.WaitAll(tasks);
            this.GatherInspectionTasksResults(tasks);

            foreach (KeyValuePair<string, Namespace> item in this.namespaces)
            {
                result.Add(item.Key, item.Value);
            }

            return result;
        }

        /// <summary>
        /// Initiates the inspection process asynchronously
        /// </summary>
        /// <param name="clientBase">The base class for the client class</param>
        /// <param name="serviceContractsNamespace">The xml namespace for the generated contracts</param>
        public void GetInterfacesAsync(string clientBase, string serviceContractsNamespace)
        {
            this.cancellationTokenSource = new CancellationTokenSource();
            
            Task<Namespace>[] tasks = this.CreateInspectionTasks(clientBase, serviceContractsNamespace);
            
            if (!this.cancellationTokenSource.IsCancellationRequested)
            {
                // Gather the data for all namespaces after all inspection tasks are done
                Task.Factory.ContinueWhenAll(tasks, (_) =>
                    {
                        if (!this.cancellationTokenSource.IsCancellationRequested)
                        {
                            // tasks[i].Result contains the resulting namespace
                            this.GatherInspectionTasksResults(tasks);
                            this.ReportResult();
                        }
                        else
                            this.ReportResult(new OperationCanceledException("Inspection cancelled"));
                    },
                    this.cancellationTokenSource.Token
                );
            }
            else
            {
                this.ReportResult(new OperationCanceledException("Inspection cancelled"));
            }

        }

        /// <summary>
        /// Cancels the inspection process
        /// </summary>
        public void CancelGetInterfaces()
        {
            if (this.cancellationTokenSource != null)
            {
                this.cancellationTokenSource.Cancel();
            }
        }

        /// <summary>
        /// IDisposable implementation
        /// </summary>
        public void Dispose()
        {
            if (this.cancellationTokenSource != null)
            {
                this.cancellationTokenSource.Dispose();
                this.cancellationTokenSource = null;
            }
        }

        #endregion

        #region Instance Methods (Private)

        private Task<Namespace>[] CreateInspectionTasks(string clientBase, string serviceContractsNamespace)
        {
            Assembly contractsAssembly = Assembly.LoadFrom(this.contractsAssemblyPath);
            // Get all public types in the provided assembly
            Type[] types = contractsAssembly.GetExportedTypes();
            // Get interface types only
            IEnumerable<Type> interfaces = types.Where(t => t.IsInterface && t.IsPublic);
            // Get interfaces marked as ServiceContract
            IEnumerable<Type> serviceContracts =
                    from i in interfaces
                    let attributes = i.GetCustomAttributes(typeof(ServiceContractAttribute), true).Cast<ServiceContractAttribute>()
                    where attributes.Count() > 0
                    select i;
            // Group interfaces by namespace
            IEnumerable<IGrouping<string, Type>> serviceContractsNamespaceGroups = serviceContracts.GroupBy(i => i.Namespace);
            int count = serviceContractsNamespaceGroups.Count();

            // Create a task to inspect each namespace found
            Task<Namespace>[] tasks = new Task<Namespace>[count];
            for (int i = 0; i < count; i++)
            {
                Tuple<string, IGrouping<string, Type>, string> state = new Tuple<string, IGrouping<string, Type>, string>(clientBase, serviceContractsNamespaceGroups.ElementAt(i), serviceContractsNamespace);
                tasks[i] = Task<Namespace>.Factory.StartNew(
                    this.CreateNamespace,
                    state,
                    this.cancellationTokenSource.Token
                    );
            }
            return tasks;
        }

        private void GatherInspectionTasksResults(Task<Namespace>[] tasks)
        {
            for (int i = 0; i < tasks.Length; i++)
            {
                this.namespaces.TryAdd(tasks[i].Result.Name, tasks[i].Result);
            }
        }

        private Namespace CreateNamespace(object state)
        {
            ParallelOptions options = new ParallelOptions { CancellationToken = this.cancellationTokenSource.Token };

            Tuple<string, IGrouping<string, Type>, string> arguments = (Tuple<string, IGrouping<string, Type>, string>)state;
            IGrouping<string, Type> grouping = arguments.Item2;
            string serviceContractsNamespace = arguments.Item3;
            Namespace nameSpace = new Namespace { Name = grouping.Key };
            ConcurrentBag<Interface> interfaces = new ConcurrentBag<Interface>();
            try
            {
                Parallel.ForEach(grouping, options, t =>
                {
                    Interface itf = new Interface { Name = t.Name, ClientBaseName = arguments.Item1, ServiceContractNamespace = serviceContractsNamespace };
                    this.ReportProgress(itf);
                    IEnumerable<MethodInfo> methodInfos = t.GetMethods().Where(m => m.GetCustomAttributes(typeof(OperationContractAttribute), false).Length > 0);
                    itf.AddMethods(methodInfos.Select(m =>
                        new Method
                        {
                            Name = m.Name,
                            OverloadedName = this.GetOverloadedName(m),
                            ReturnType = this.GetTypeName(m.ReturnType),
                            Parameters = m.GetParameters().Select(p => new Parameter { Name = p.Name, Type = this.GetTypeName(p.ParameterType) }).ToList()
                        }));
                    IEnumerable<ServiceKnownTypeAttribute> knownTypesAttributes = t.GetCustomAttributes(typeof(ServiceKnownTypeAttribute), false).Cast<ServiceKnownTypeAttribute>();
                    itf.AddServiceKnownTypes(knownTypesAttributes.Select(a => a.Type.FullName));

                    interfaces.Add(itf);
                });
            }
            catch (OperationCanceledException ex)
            {
                this.ReportResult(ex);
            }

            nameSpace.Interfaces.AddRange(interfaces);
            return nameSpace;
        }

        #endregion
    }
}
